import logging
import re, json

from django import http
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import DatabaseError
from django.shortcuts import render, redirect
from django.urls import reverse
from django_redis import get_redis_connection

from carts.utils import merge_cart_cookies_redis
from celery_tasks.email.tasks import send_verify_email
from users.constants import USER_ADDRESS_COUNTS_LIMIT
from users.models import User, Address
from goods.models import SKU

# Create your views here.
from django.views import View

from django_meiduo.utils.views import LoginRequiredJSONMixin
from django_meiduo.utils.response_code import RETCODE

from users.utils import generate_verify_email_url, check_verify_email_token

# 创建日志输出器
logger = logging.getLogger('django')


class UsernameCountView(View):
    """判断用户名是否重复注册"""

    def get(self, request, username):
        """
        :param username:用户名
        :return: JSON
        """
        # 接收和校验参数(urls已完成)K
        # 实现主体业务逻辑：使用username查询对应的记录的条数(filter返回的是满足条件的结果集，count()是结果集的数量)
        count = User.objects.filter(username=username).count()
        # 响应结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})


class MobileCountView(View):
    """判断手机号码是否重复注册"""

    def get(self, request, mobile):
        """
        :param mobile: 手机号码
        :return: json
        """
        count = User.objects.filter(mobile=mobile).count()
        # 相应结果
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'count': count})


class RegisterView(View):
    """用户的注册"""

    def get(self, request):
        """提供用户注册页面"""
        return render(request, 'register.html')

    def post(self, request):
        """实现用户注册业务逻辑"""
        # 接收参数:表单参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        password2 = request.POST.get('password2')
        mobile = request.POST.get('mobile')
        allow = request.POST.get('allow')
        sms_code_cllient = request.POST.get('sms_code')

        # 校验参数:前后端校验分开，避免恶意用户越过前端逻辑发送请求，要保证客户端的安全，前后端的校验逻辑要相同
        # 判断参数是否齐全：all([列表])：会去校验列表中的元素是否为空，如果只要有一个为空，则返回false
        if not all([username, password, password2, mobile, allow]):
            return http.HttpResponseForbidden("缺少必传参数")

        # 判断用户名是否是5-20字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden("请输入5-20个字符的用户名")

        # 判断密码是否是8-20字符
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            http.HttpResponseForbidden("请输入8-20个字符的密码")

        # 判断两次输入的密码是否一致
        if password != password2:
            http.HttpResponseForbidden("两次输入的密码不一致")

        # 判断手机号码是否合法
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            http.HttpResponseForbidden("请输入合法的手机号码")

        # 判断短信验证码是否输入正确
        redis_conn = get_redis_connection('verify_code')
        sms_code_server = redis_conn.get('sms_%s' % mobile)
        if sms_code_server is None:
            return render(request, 'register.html', {'sms_code_errmsg': '短信验证码已失效'})
        if sms_code_cllient != sms_code_server.decode():  # python3从redis数据库里存取的是bytes类型，需要转成字符串
            return render(request, 'register.html', {'sms_code_errmsg': '输入的短信验证码有误'})

        # 判断用户是否勾选了协议
        if allow != 'on':
            http.HttpResponseForbidden("请您勾选用户协议")

        # return render(request, 'register.html', {'regester_errmsg': '注册失败'})   # 测试注册失败

        # 保存注册数据：是注册业务的核心
        try:
            user = User.objects.create_user(username=username, password=password, mobile=mobile)
        except DatabaseError:
            return render(request, 'register.html', {'regester_errmsg': '注册失败'})
        login(request, user)  # 状态保持
        # 响应结果：重定向到首页
        # return http.HttpResponse("注册成功，重定向到首页")
        # return redirect('/') 弊端：如果某天修改路由的匹配为index，则这里也需要改
        # return redirect(reverse('contents:index'))
        # 相应结果
        response = redirect(reverse('contents:index'))

        # 为了实现在首页的右上角展示用户名信息，我们需要将用户名缓存到cookie中
        # response.set_cookie('key','val','expiry')
        response.set_cookie('username', user.username, max_age=3600 * 24 * 15)

        return response


class LoginView(View):
    """用户登录"""

    def get(self, request):
        """提供用户登录界面"""
        return render(request, 'login.html')

    def post(self, request):
        """提供用户登录逻辑"""
        # 接收参数
        username = request.POST.get('username')
        password = request.POST.get('password')
        remembered = request.POST.get('remembered')
        # 校验参数
        if not all([username, password]):
            return http.HttpResponseForbidden('缺少必传参数')
        # 判断用户名是否是5-20字符
        if not re.match(r'^[a-zA-Z0-9_-]{5,20}$', username):
            return http.HttpResponseForbidden("请输入5-20个字符的用户名")

        # 判断密码是否是8-20字符
        if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
            http.HttpResponseForbidden("请输入8-20个字符的密码")
        # 认证用户
        user = authenticate(username=username, password=password)
        if user is None:
            return render(request, 'login.html', {'account_errmsg': '账号或密码错误'})
        # 状态保持
        login(request, user)
        if remembered != 'on':
            # 没有记住登录：状态保持在浏览器会话结束后就销毁
            request.session.set_expiry(0)  # 单位是秒
        else:
            # 记住登录：状态保持周期为两周:默认是两周
            request.session.set_expiry(None)

        # 相应结果
        # 先取出next
        next = request.GET.get('next')
        if next:
            # 重定向到next
            response = redirect(next)
        else:
            # 重定向到首页
            response = redirect(reverse('contents:index'))

        # 为了实现在首页的右上角展示用户名信息，我们需要将用户名缓存到cookie中
        # response.set_cookie('key','val','expiry')
        response.set_cookie('username', user.username, max_age=3600 * 24 * 15)

        # 用户登录成功，合并cookie购物车到redis购物车
        response = merge_cart_cookies_redis(request=request, user=user, response=response)

        return response


class LogoutView(View):
    """用户退出登录"""

    def get(self, request):
        """实现用户退出登录的逻辑"""
        # 清除状态保持信息
        logout(request)

        response = redirect(reverse('contents:index'))

        # 删除cookie中的用户名信息
        response.delete_cookie('username')

        # 响应结果
        return response


class UserInfoView(LoginRequiredMixin, View):
    """用户中心"""

    def get(self, request):
        """提供用户中心页面"""
        # 判断用户是否登录
        # if request.user.is_authenticated:
        #     return render(request, 'user_center_info.html')
        # else:
        #     return redirect(reverse('users:login'))
        # 升级版用户是否登录，可以跳转到相关用户中心页面
        # login_url='/login/'  根据源代码可知，该参数可以不写，也可以写，写的话则进入自己写的这个页面，不写的话则进入dev的LOGIN_URL的页面
        # redirect_field_name = 'redirect_to'  根据源代码可知，该参数可以不写，也可以写，写的话则按照自己写的这个内容进行重定向，有默认值，可按照默认值即可（next）,登录完成后回到起点[此方法需要自己做]

        # 如果LoginRequiredMixin判断出用户已登录，那么request.user 就是登录用户对象
        context = {
            'username': request.user.username,
            'mobile': request.user.mobile,
            'email': request.user.email,
            'email_active': request.user.email_active
        }
        return render(request, 'user_center_info.html', context)


class EmailView(LoginRequiredJSONMixin, View):
    """添加邮箱"""

    def put(self, request):
        # 接收参数
        json_str = request.body.decode()  # 从body中拿到json字符串,body的类型是bytes，所以要用decode去转换一下
        json_dict = json.loads(json_str)  # 将json字符串转换成json的字典
        email = json_dict.get('email')  # 从字典中取出email的值

        # 校验参数
        if not email:
            return http.HttpResponseForbidden('缺少email参数')
        if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
            return http.HttpResponseForbidden('参数email有误')

        # 将用户传入的邮箱保存到登录用户数据库的email字段中
        try:
            request.user.email = email  # 当前登录用户的对象绑定email字段
            request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({
                'code': RETCODE.DBERR,
                'errmsg': '添加邮箱失败'
            })

        # 发送邮箱的验证邮件
        verify_url = generate_verify_email_url(request.user)
        # send_verify_email(email, verify_url)  # 写法，一定要记得调用delay
        send_verify_email.delay(email, verify_url)

        # 相应结果
        return http.JsonResponse({
            'code': RETCODE.OK,
            'errmsg': 'OK'
        })


class VerifyEmailView(View):
    """验证邮箱"""

    def get(self, request):
        # 接收参数
        token = request.GET.get('token')
        # 校验参数
        if not token:
            return http.HttpResponseForbidden('缺少token')
        # 从token中提取用户的信息user_id => uesr
        user = check_verify_email_token(token)
        if not user:
            return http.HttpResponseForbidden('无效的token')
        # 修改用户的email_active = True
        try:
            user.email_active = True
            user.save()
        except Exception as e:
            logger.error(e)
            return http.HttpResponseServerError('激活邮箱失败')
        # 相应结果
        return redirect(reverse("users:info"))


class AddressView(LoginRequiredMixin, View):
    """用户收获地址"""

    def get(self, request):
        """查询并展示用户的地址信息"""
        # 获取登录信息
        login_uesr = request.user
        # 使用当前登录用户和is_deleted = False 作为条件查询地址数据
        addresses = Address.objects.filter(user=login_uesr, is_deleted=False)
        # 将用户地址模型列表转成字典列表：因为渲染的时候不是使用Jinja2模板引擎渲染的，JSON不认识，Vue.js也不认识
        address_list = []
        for address in addresses:
            address_dict = {
                "id": address.id,
                "title": address.title,
                "receiver": address.receiver,
                "province": address.province.name,
                "city": address.city.name,
                "district": address.district.name,
                "place": address.place,
                "mobile": address.mobile,
                "tel": address.tel,
                "email": address.email
            }
            address_list.append(address_dict)
        # 构造上下文
        context = {
            # 'default_address_id': login_uesr.default_address_id,   # 没有默认的地址返回的就是None
            'default_address_id': login_uesr.default_address_id or '0',  # 0仅仅表示占位，无实际意义
            # 解决方案，如果没有默认地址，就随便写一个字符串，让html继续运行，因为html中有v-cloak，所以也不会报错，因为没有执行完毕
            'addresses': address_list,
        }

        return render(request, 'user_center_site.html', context)

    pass


class AddressCreateView(LoginRequiredJSONMixin, View):
    """新增地址"""

    def post(self, request):
        """实现新增地址逻辑"""

        # 判断用户地址数量是否超过上限：查询当前登录用户的地址数量（放在这里，参数超过了直接不接收参数）
        # count = Address.objects.filter(user=requeset.user).count()    可用，但是比较低级
        count = request.user.addresses.count()  # 一查多，使用related_name查询
        if count > USER_ADDRESS_COUNTS_LIMIT:
            return http.JsonResponse({
                'code': RETCODE.THROTTLINGERR,
                'errmsg': '超出用户地址上限'
            })

        # 接收参数
        # json_str=requeset.body.decode()
        # json_dict=json.loads(json_str)
        # json_dict.get('key')
        json_dict = json.loads(request.body.decode())
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')

        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        # 保存用户传入的地址信息
        try:
            address = Address.objects.create(
                user=request.user,
                title=receiver,  # 默认就是收货人
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email,
                # is_deleted = mobile.BooleanField(default = False,verbose_name = '逻辑删除')
            )
            # 如果用户没有默认的地址，我们需要指定默认地址
            if not request.user.default_address:  # 如果没有，返回false
                request.user.default_address = address
                request.user.save()

        except Exception as e:
            logger.error(e)
            return http.JsonResponse({
                'code': RETCODE.DBERR,
                'errmsg': '新增地址失败'
            })
        # 新增地址成功，将新增的地址响应给前端实现局部更新
        address_dict = {
            'id': address.id,
            'title': address.title,
            'receiver': address.receiver,
            'province': address.province.name,
            'city': address.city.name,
            'district': address.district.name,
            'place': address.place,
            'mobile': address.mobile,
            'tel': address.tel,
            'email': address.email
        }

        # 相应新增地址结果：需要将新增的地址返回给前端渲染
        return http.JsonResponse({
            'code': RETCODE.OK,
            'errmsg': '新增地址成功',
            'address': address_dict
        })


class UpdateDestoryAddressView(LoginRequiredJSONMixin, View):
    """更新和删除地址"""

    def put(self, request, address_id):
        """更新地址"""
        # 接收参数
        # json_str=requeset.body.decode()
        # json_dict=json.loads(json_str)
        # json_dict.get('key')
        json_dict = json.loads(request.body.decode())
        receiver = json_dict.get('receiver')
        province_id = json_dict.get('province_id')
        city_id = json_dict.get('city_id')
        district_id = json_dict.get('district_id')
        place = json_dict.get('place')
        mobile = json_dict.get('mobile')
        tel = json_dict.get('tel')
        email = json_dict.get('email')

        # 校验参数
        if not all([receiver, province_id, city_id, district_id, place, mobile]):
            return http.HttpResponseForbidden('缺少必传参数')
        if not re.match(r'^1[3-9]\d{9}$', mobile):
            return http.HttpResponseForbidden('参数mobile有误')
        if tel:
            if not re.match(r'^(0[0-9]{2,3}-)?([2-9][0-9]{6,7})+(-[0-9]{1,4})?$', tel):
                return http.HttpResponseForbidden('参数tel有误')
        if email:
            if not re.match(r'^[a-z0-9][\w\.\-]*@[a-z0-9\-]+(\.[a-z]{2,5}){1,2}$', email):
                return http.HttpResponseForbidden('参数email有误')

        # 使用最新的地址信息覆盖指定的旧的地址信息
        # address = Address.objects.get(id=address_id)
        # address.title=''
        # ……
        # address.save()        这样写可以，但是比较麻烦，
        try:
            Address.objects.filter(id=address_id).update(
                user=request.user,
                title=receiver,
                receiver=receiver,
                province_id=province_id,
                city_id=city_id,
                district_id=district_id,
                place=place,
                mobile=mobile,
                tel=tel,
                email=email
            )
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({'code': RETCODE.DBERR, 'errmsg': '更新地址失败'})

        # 响应新的地址信息给前端
        # 新增地址成功，将新增的地址响应给前端实现局部更新
        address = Address.objects.get(id=address_id)  # 可try
        address_dict = {
            'id': address.id,
            'title': address.title,
            'receiver': address.receiver,
            'province': address.province.name,
            'city': address.city.name,
            'district': address.district.name,
            'place': address.place,
            'mobile': address.mobile,
            'tel': address.tel,
            'email': address.email
        }

        # 相应新增地址结果：需要将新增的地址返回给前端渲染
        return http.JsonResponse({
            'code': RETCODE.OK,
            'errmsg': '修改地址成功',
            'address': address_dict
        })
        pass

    def delete(self, request, address_id):
        """删除地址"""
        # 实现指定地址的逻辑删除，is_delete = True
        try:
            address = Address.objects.get(id=address_id)
            address.is_deleted = True
            address.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({
                'code': RETCODE.DBERR,
                'errmsg': '删除地址失败'
            })
        # 响应结果
        return http.JsonResponse({
            'code': RETCODE.OK,
            'errmsg': '删除地址成功'
        })


class DefaultAddressView(LoginRequiredJSONMixin, View):
    """设置默认地址"""

    def put(self, request, address_id):
        """实现设置默认地址逻辑"""
        try:
            # 查询出当前哪个地址会作为登录用户的默认地址
            address = Address.objects.get(id=address_id)

            # 将指定的地址设置为当前登录用户的默认地址
            request.user.default_address = address
            request.user.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({
                'code': RETCODE.DBERR,
                'errmsg': '设置默认地址失败'
            })
        return http.JsonResponse({
            'code': RETCODE.OK,
            'errmsg': '设置默认地址成功'
        })


class UpdateTitleAddressView(LoginRequiredJSONMixin, View):
    """修改地址标题"""

    def put(self, request, address_id):
        """实现更新地址标题逻辑"""
        # 接收参数
        json_dict = json.loads(request.body.decode())
        title = json_dict.get('title')
        # 校验参数  # 查询的是数据库的内容，可不写
        if not title:
            return http.HttpResponseForbidden('缺少title')
        try:
            # 查询当前要更新标题的地址信息
            address = Address.objects.get(id=address_id)
            # 将新的地址标题覆盖地址标题
            address.title = title
            address.save()
        except Exception as e:
            logger.error(e)
            return http.JsonResponse({
                'code': RETCODE.DBERR,
                'errmsg': '修改地址标题失败'
            })
        # 响应结果
        return http.JsonResponse({
            'code': RETCODE.OK,
            'errmsg': '修改地址标题成功'
        })


class UserBrowseHistory(LoginRequiredJSONMixin, View):
    """用户的浏览记录"""

    def post(self, request):
        """保存用户商品浏览记录"""
        # 接收参数
        json_str = request.body.decode()
        json_dict = json.loads(json_str)
        sku_id = json_dict.get('sku_id')
        # 校验参数
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return http.HttpResponseForbidden('sku_id错误')
        # 保存sku_id到redis
        redis_conn = get_redis_connection('history')
        user = request.user
        # # 先去重
        # redis_conn.lrem('history_%s' % user.id, 0, sku_id)
        # # 再保存：最近浏览的商品在最前面
        # redis_conn.lpush('history_%s' % user.id, sku_id)
        # # 最后截取
        # redis_conn.ltrim('history_%s' % user.id, 0, 4)

        # 提升用户响应效率，用管道
        pl = redis_conn.pipeline()
        # 先去重
        pl.lrem('history_%s' % user.id, 0, sku_id)
        # 再保存：最近浏览的商品在最前面
        pl.lpush('history_%s' % user.id, sku_id)
        # 最后截取
        pl.ltrim('history_%s' % user.id, 0, 4)
        # 执行
        pl.execute()

        # 响应结果
        return http.JsonResponse({
            'code': RETCODE.OK,
            'errmsg': 'OK'
        })

    def get(self, request):
        """查询用户商品浏览记录"""
        user = request.user
        redis_conn = get_redis_connection('history')
        sku_ids = redis_conn.lrange('history_%s' % user.id, 0, -1)

        skus = []
        for sku_id in sku_ids:
            sku = SKU.objects.get(id=sku_id)
            skus.append({
                'id': sku.id,
                'name': sku.name,
                'price': sku.price,
                'default_image_url': sku.default_image.url
            })
        return http.JsonResponse({
            'code': RETCODE.OK,
            'errmsg': 'OK',
            'skus': skus
        })
